# Ultrasonic Distance perception ***Copyright © Quectel Wireless Solutions Co., Ltd. 2026. All rights reserved.*** --- This project is an ultrasonic distance sensing example implemented based on the Quectel Pi M2 intelligent main control board. The system performs real-time ranging via an RGB ultrasonic module, and the on-board PWM drives the servo to rotate the probe for scanning within the range of 0~180°; the full-circle readings are divided by orientation into **left / middle / right** three sectors, based on which the system makes a judgment of "which side the nearest object is on", and presents the data in real time on the graphical interface via top-down polar plots, distance curves and other forms. ```{image} images/image_MzPubc73WoxL76xhX8ycOzbmnxe.webp :width: 2289px :height: 1080px :align: center ``` # Collection of Development Resources ## List of Development Accessories > Note:tips > > Development accessories are for reference only, and may vary in different Development Environments | **Parts Name** | **Quantity** | **Specifications** | | --- | --- | --- | | Quectel Pi M2 Intelligent Main Control Board | 1 piece | - | | Buck Regulator Module | 1 piece | **Multi-channel independent output**[**adjustable step-down voltage regulator module**]() | | 7.4V Lithium Battery (2S) | 1 piece | [18650 Li-ion battery pack 7.4V]() | | Iridescent RGB Ultrasonic Module | 1 piece | [RGB ultrasonic module]() | | TXS0108E Bidirectional Voltage-Level Translation Module | 1 piece | [8-channel level conversion module]() | | SG90 Servo Gimbal | 1 piece | [SG90 Servo Gimbal]() | | dupont wire | several | Female to female, it is recommended to keep the length within 20cm to reduce interference | | display screen | 1 unit | [8-inch touchscreen]() HDMI monitors are also compatible | | USB keyboard and mouse (optional) | 1 set | Graphical User Interface Operation | > Note:tips > > This example uses a **step-down voltage regulator module** for unified power supply: the main control board and the servo are connected to **different output channels** of the module respectively, which not only eliminates the need for an independent power supply, but also naturally isolates the interference of the servo current on the main control board. Do not connect the servo and the main control board to the same output channel. ## Parts Physical Reference ```{image} images/image_E2HTb7eUMor0Z8xtGSacWRBHnnd.webp :width: 915px :height: 439px :align: center ``` # Quick Start ## Development Preparation **Quectel Pi M2** intelligent main control board comes pre-installed with **Debian 13** system mirror by default, so there is no need to flash the mirror again. This example requires the use of **I2C7** and **onboard PWM (PWM2_CH3)** two peripheral devices, among which: - I2C7 is enabled by default, and its corresponding device node is `/dev/i2c-7`; - The PWM channel requires root privileges **to be exported once** and have its permissions unlocked (the kernel sysfs `export` and the exported attributes are writable only by root by default). Simply run the script `deploy/setup_servo_pwm.sh` once, which will automatically install the boot auto-start service and udev rules, so no further authorization is needed even after rebooting. ## hardware connection Complete all the following hardware connection operations, with the serial numbers consistent with those in the table below: ```{image} images/image_SrMKbxJZdoDERRxmRWfc0JUFnHh.webp :width: 627px :height: 463px :align: center ``` 1. **Ultrasonic module connection:** The module is a 5V device, while the GPIO of the main control board operates at 3.3V logic level, **so it must be connected to the main control board after level shifting** (the 4 pins of the module are ordered as `VCC / SDA / SCL / GND`, reverse connection may damage the module; the onboard `IIC / UART` pads of the module must be set to the **I2C** position). The module's **VCC** and the 5V side (**VCCB**) of the level shifting module are both powered from the 5V network of the whole device (in this example, they are tapped at pin **Pin2** and **Pin4** of the header respectively);Module **SDA / SCL** is connected to the level shifter via **B1 / B2**, and then connected to Pin27 / Pin28 of the main control board via **A1 / A2**. **Pin27 / Pin28** 2. **Servo Connection: Connect the servo signal wire to Pin 33 on the main control board; connect the power wire to the adjustable output terminal of the step-down voltage regulator module** (adjusted to 5.0~5.5V), and the ground wire of this output channel must be connected to the main control board for **common grounding**. 3. **Power supply connection:** Connect the 7.4V lithium battery to the input terminal of the step-down voltage regulator module; after verifying that the two outputs are correct with a multimeter, connect the module's **5V fixed output terminal** to the main control board's **Pin2 (5V)** and **Pin14 (GND)**, and connect the **adjustable output terminal** to the servo; the system will be powered on at this point. > Note:tips > > When powering the main control board via the 40-pin header, **do not connect a USB-C power supply at the same time** to avoid mutual backflow between the two 5V power rails; use either one or the other. Additionally, ensure that the output of the selected power rail is stably regulated at 5V (must not exceed 5.25V), as overvoltage will damage the main control board. The specific pin correspondence is as follows: | **Quectel Pi M2 Pins** | **connection object** | **Description** | | --- | --- | --- | | Pin1(3V3) | Level Conversion Module **VCCA** | 3.3V side reference voltage, supplied by the main control board | | Pin9 (GND) | Level Conversion Module **GND** | 3.3V side ground reference | | Pin4(5V) | Level Conversion Module **VCCB** | 5V side reference voltage (belongs to the same pin header 5V network as Pin2) | | Pin2(5V) | Ultrasonic Module **VCC** | Module power supply: 5V (the module supports 2.8~5.5V) | | Pin27(I2C7_SDA) | Level Conversion Module **A1** | 3.3V Side Data | | Pin28(I2C7_SCL) | Level Conversion Module **A2** | 3.3V side clock | | Level Conversion Module **B1 / B2** | Ultrasonic Module **SDA / SCL** | 5V side Data / Clock | | Pin2(5V) | Buck converter module **5V fixed output (+)** | Power the main control board; do not reverse the connection and do not exceed 5.25V | | Pin14 (GND, any GND pin of the header will work) | Buck Converter Module **GND Terminal** | Main control board ground, which also serves as the common ground for the entire system | | **Pin33(PWM)** | Servo **Signal Wire** (Orange/Yellow) | PWM2_CH3, corresponding to `2ade3000.pwm` | | Buck Converter Module **5V Fixed Output (+)** | Ultrasonic Module **VCC**, Level Conversion Module **VCCB** | Power can also be drawn separately from Pin 2 / Pin 4 of the header, as they belong to the same 5V network | | Buck converter module **Adjustable output (+)**(5.0~5.5V) | servo **power cable** (red) | **Use a separate circuit**; do not share the same output circuit with the main control board | | Buck Converter Module **GND Terminal** | servo ground wire, ultrasonic module GND, level conversion module GND, main control board GND | All ground wires are connected to the GND terminal of the step-down module, belonging to the same ground network. | > Note:tips > > The instantaneous current of a servo during stall or startup can reach 500~700 mA. If the servo and the main control board share the same output channel, this inrush current will temporarily pull down the 5V rail, which may cause both the ultrasonic module and the level conversion module to go offline simultaneously (the symptom is continuous ranging failure after rotating to certain angles). This example uses the **two independent outputs** of the buck module to supply power separately, which inherently avoids this interference. ## Project Implementation ### Get Code Open the terminal on the intelligent main control board, and use git to clone the project code (please replace the repository address with the actual one before release). ```bash git clone https://github.com/Quectel-Pi/demo-ultrasonic-sensing.git ``` After execution is complete, a demo-ultrasonic-sensing folder should be generated in the current directory. ### Install Dependencies Navigate to the project directory, create a virtual environment, and install the only dependency: PySide6. ```bash cd demo-ultrasonic-sensing python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt ``` > Note:tips > > The python3 preinstalled on the board usually does not come with the pip module, and sudo requires a password, so **do not** try `sudo apt install python3-pip`; the environment created with `python3 -m venv` comes with pip by default. ### PWM Authorization One-time authorization for servo PWM (root password required). ```bash sudo bash deploy/setup_servo_pwm.sh ``` You are done once the terminal shows an authorization success prompt. The script features idempotence, so running it repeatedly will not cause side effects;`--status` allows you to check the status only, `--revert` enables you to uninstall the auto-start service on boot. ### Verification Environment Output *6.9.1* indicates that the dependency has been installed successfully. ```bash .venv/bin/python -c "import PySide6; print(PySide6.__version__)" ``` ### Run the program Run the startup script in the project root directory *run_scan.py* (it will automatically select the interpreter with PySide6, regardless of the directory from which it is invoked); if the program starts normally, a graphical interface window will open. ```bash cd demo-ultrasonic-sensing python3 run_scan.py ``` ## Interface Description The left column consists of two views (scanning view + distance history), and the right column is the Console. On the right Console **, only three group boxes are constantly displayed**—— `Distance` (real-time distance), `Detection threshold` (detection threshold), `Rotate & Scan` (scan and conclusion); above them are the panel title, I2C subtitle and link status line. The remaining **four partitions are collapsed by default** and can be expanded by clicking the title bar (among which `Probe direction & scan step` is embedded in `Rotate & Scan`). | **region** | **Display Status** | **Content** | | --- | --- | --- | | Top-left Scan View | Autosomal dominant | Top-down polar plot. The center of the circle is the rotatable probe (R/T transducer + two RGB LEDs), the upper semicircle from 0° to 180° corresponds to the servo travel, and the sectors are labeled `LEFT`/ `CENTER`/ `RIGHT`; the red dashed arc indicates the current detection threshold; each sampling point is drawn as a small square (dim for the previous cycle, bright for the current cycle), with the closest point marked by a highlighted circle and distance annotation; the top-left corner directly displays `Nearest object: …` | | Bottom Left, Distance History | Autosomal dominant | `Distance history (last 30 s)`: Distance curve for the most recent 30 seconds. No lines are connected at points where reading fails, **short gaps (≤2 sampling points) are bridged with dashed lines**; the yellow dashed line indicates the detection threshold | | Right `Distance` | Autosomal dominant | Large-font readings and a status line indicating **whether there are any obstacles** within the detection range:`● Obstacle within detection range` (Red) / `○ No obstacle within detection range` (Green) | | Right side `Detection threshold` | autosomal dominant | Threshold slider. This value not only determines the status of the line above, but also serves as the criterion for the orientation judgment of "whether the reading falls within the range". | | Right side `Rotate & Scan` | autosomal dominant | Servo status (including polarity), `⟳ Rotate & Scan (180°)` button, `Continuous scan (repeat)` continuous scan switch, `Enable servo` servo enable switch, and the details in the conclusion area: `NEAREST OBJECT`+ sector + distance breakdown | | Right side `▸ Probe direction & scan step` | Collapse (embedded in `Rotate & Scan`) | Probe orientation relative to the slider (0°: left, 90°: front, 180°: right) and scanning step | | Right side `▸ Link statistics` | Collapse | Refresh rate, single time consumption, **cumulative missing points**, raw reading, median window | | Right side `▸ Indicator LEDs` | Collapse | Obstacle indication switch, target probe (dual probe / R receive / T transmit), 8 preset colors, custom color | | Right side `▸ Event log` | Collapse | Activity Log; when collapsed, the title displays the number of unread items (e. g., `Event log (5)`), which resets to zero once expanded | | Bottom Status Bar | Autosomal dominant | I2C Node · Servo Status · scanning/static · Target Refresh Rate · Threshold | ## Scanning Method and Orientation Determination There are two triggering modes for scanning, which share the same set of scanning and judgment logic: | **Method** | **Trigger Point** | **Behavior** | | --- | --- | --- | | **Single Scan** (Default) | Click the `⟳ Rotate & Scan (180°)` button | Complete a full scan cycle → Output the result → **The servo returns to the 90° home position**. During scanning, the button displays `Scanning… n/N` and is disabled; repeated clicks will not queue additional re-scan commands | | Continuous Scanning | Click the `Continuous scan (repeat)`(Or `--auto-scan`) | Loop one after another, refresh the conclusion once per loop; return to the docking position after unchecking | The interface only uses **one angular coordinate system (observation angle)**: 0° = left, 90° = directly ahead, 180° = right. The servo angle and the observation angle are in a mirror relationship under the physical assembly of this example (factory parameter `SERVO_INVERT = True`): | **viewing angle of interface** | **0°(Left)** | **90°(directly ahead)** | **180°(Right)** | | --- | --- | --- | --- | | Corresponding servo angle | 180° | 90° | 0° | > This mirror relationship only clarifies whether "0° counts as left or right", and has nothing to do with pulse width or stroke calibration; if your gimbal is assembled in the opposite direction, just flip `src/config.py` in `SERVO_INVERT`, and the 90° parking angle is directly ahead in all cases. Judgment Rule (Conclusion = **the sector where the nearest object is located**): 1. Only valid readings **falling within the detection range** (≤ threshold) are counted, and sampling points with no echo (reading = 0) are directly discarded; 2. Classify the full-circle readings into three sectors according to the observation angle: `Left` 0~60°, `Center` 60~120°, `Right` 120~180°; 3. Take from each sector **the nearest** point respectively; 4. The one with the minimum distance among the three sectors is the conclusion; 5. Sectors that differ from the conclusion by less than `DIRECTION_MARGIN_CM` (3 cm by default) will only be displayed as the `also close: …` prompt in the detail line, and will not alter the conclusion; 6. When there are no readings within range for all three sectors, the system will output `NONE IN RANGE`, while the detail line will still display the most recent full-circle reading. # Implementation Principle ## ranging The module uses the I2C interface, and the procedure is: write to register `0x10` the value of `0x01` to trigger a single measurement, wait for approximately 100 ms, then read 2 bytes of big-endian data from the module. The distance conversion relationship is as follows: `Distance (cm) = Original value × 0.017`, with an effective measurement range of 2 to 400 cm. The driver layer will discard readings that exceed the range (for example, `0x7FFF` caused by I2C bit flipping), so abnormal values will not be introduced into the interface and judgment logic. ## servo The servo is **directly driven by the kernel sysfs PWM** (software PWM is not used to avoid angle jitter caused by User Mode jitter), with a frequency of 50 Hz, and a pulse width of 500~2500 µs corresponding to 0~180°. The driver searches for channels based on the **PWM controller device name** (`2ade3000.pwm`) instead of the `pwmchipN` numbering, as the numbering changes with the detection order of the kernel and device tree. The on-board channel used in this example is `Pin33 = PWM2_CH3 = pwmchip2/pwm0`. Two core constraints encountered and addressed in board-level testing: - The channel must first be written with a non-zero `period`, after which `enable`/ `duty_cycle` can be written; otherwise, `EINVAL` is returned. - The default polarity of this channel after export is `inversed`, while the servo requires `normal`, and the driver will explicitly correct it and display the polarity on the interface. ## smooth motion PWM servos have no on-board easing function, and the pulse width changes immediately once written. If the target angle is written in one go, the movement from the resting position of 90° to the scanning start point of 0° will be **a positional step**: the internal controller of the servo will run at full speed, producing loud gear impact noise, being prone to overshoot, and generating current spikes. Therefore, the drive operates according to **a trapezoidal velocity profile** to approach the target angle in discrete steps: it first accelerates at the specified acceleration limit, then runs at constant speed, and finally decelerates to 0 such that it can come to a complete stop within the remaining distance. A single large-angle movement is split into dozens of small steps for issuance, and the measured time consumption is as follows: | **displacement** | **5°(single step)** | **30°** | **90°(berth → scanning start point)** | **180°(full stroke)** | | --- | --- | --- | --- | --- | | Time elapsed | 0.065 s | 0.175 s | 0.425 s | 0.800 s | Since the speed at the time of positioning is close to zero, the mechanical residual vibration is significantly reduced, and the stable waiting time is shortened, which just offsets the overhead of the ramp itself. ## Performance Measurement Data | **Project** | **Measurement results** | | --- | --- | | Time consumption of single ranging | Approx. 100 ms for measurement + approx. 60 ms for communication overhead | | static refresh rate | Approximately 5 Hz (10 out of 10 clean reads at a 200 ms cycle) | | Default 5° step to sweep one full circle | 37 sampling angles, **11.3 s** | | Repeatability of steering gear positioning | 13 points at different angles over two consecutive laps coincide with a precision of 0.1 cm | | Intrinsic Packet Loss Rate of Module | **5~7%**(Control experiment: the final effective rates for immediate re-reading 0/1/2 times are 93.3% / 95.0% / 93.3% respectively) | | Interference of the steering gear on ranging | None (the efficiency remains consistent across the three states: hold, release, and sweep) | > Point loss is an inherent characteristic of the module, therefore the interface layer adopts the presentation method of "short-term hold + dashed curve bridging": when consecutive point loss does not exceed `HOLD_FAILS` (default: 3) times, the previous reading is displayed (the number is dimmed); for gaps of ≤2 points in the historical curve, dashed line segments are used for bridging to distinguish from real data; the link statistics area provides **cumulative point loss** counts to facilitate judging whether the issue is truly improved. # Application Demo ### Graphical User Interface Static interface state (real hardware, readings within the detection range, status bar displays `Obstacle within detection range`): ```{image} images/image_EviQb1yxjoWyNLxyhHOc97MEnXd.webp :width: 1320px :height: 860px :align: center ``` Click `⟳ Rotate & Scan (180°)`, and the scan will be in progress: the button will change to `Scanning… n/N`, and the sampling points on the polar plot will accumulate point by point as the scan proceeds: ```{image} images/image_VpQvbkWHsohPgIxiTDkcEUJ3nBd.webp :width: 1320px :height: 860px :align: center ``` Orientation determination result after one full scan (presented simultaneously in the conclusion area and at the top-left of the scan image `Nearest object`): ```{image} images/image_Pkbublu1eoAQ1xxAXDycWpoqnBf.webp :width: 1320px :height: 860px :align: center ``` ### Demo Video
ultrasonic_distance_perception
# Frequently Asked Questions and Solutions ## The interface displays SIMULATION (simulation mode) **Phenomenon:** ``` Status bar: SIMULATION (/dev/i2c-7 not opened) Link row: ○ SIMULATION · I2C bus not opened ``` (If the bus fails to open in real device mode, the link line will include the specific reason, for example, `SIMULATION · cannot open I2C bus /dev/i2c-7: [Errno 2] No such file or directory`.) **Solution:** - Make sure that the ultrasonic module is properly connected and powered normally, the level conversion module has 3.3V on side A and 5V on side B, and the direction is not connected reversely. - Check whether the device node exists: ```shell ls -la /dev/i2c* ``` - Verify that the current user is in the `i2c` group (check via the `groups` command); if not, re-login after adding the user to the group. - Check the interface `Event log` or the specific error cause in the status bar. ## The servo motor does not rotate, and the log indicates no write permission for sysfs **Phenomenon:** ``` No write access to /sys/class/pwm (kernel default: root only). Run this ONCE on the board: sudo bash deploy/setup_servo_pwm.sh ``` The servo status line on the interface displays the simulation state (`◐ SIMULATION · No write access to …`), and the servo will not actually rotate. **Solution:** The PWM channel requires a one-time authorization. Please run the following command in the root directory of the project: ```bash sudo bash deploy/setup_servo_pwm.sh ``` The script will export channels, release the `dialout` permission, and install the boot auto-start service and udev rules, so no repeated authorization is required afterwards. Run `sudo bash deploy/setup_servo_pwm.sh --status` to only check the current authorization status. ## Auto-scan checkbox unclickable / automatically bounces back **Phenomenon:** `Continuous scan (repeat)` Once ticked, it will immediately revert to the unticked state, with a description provided in the log: ``` Servo PWM is not actually driven (…), so the probe never turns and any direction result would be meaningless — automatic scanning is disabled ``` **Solution:** This is **by design**, not a malfunction. When the combination of "real ultrasonic wave + simulated steering gear" occurs (i. e., the ranging relies on real hardware, but the steering gear will not actually rotate due to unauthorized PWM), the program will refuse to scan: at this time, the probe always faces the same direction, and all orientation results are false. Just complete the PWM authorization according to the previous instruction to resume normal operation. ## Excessive noise when the steering gear rotates, and severe jerk at start-up **Phenomenon:** When the steering gear rotates from the docking position to the scanning start point, it produces loud noise with obvious gear collision sound. **Solution:** - Confirm that you are running on the latest version (motion smoothing is enabled by default). If you have manually disabled it before, please verify that `src/config.py` contains a non-zero value for `SERVO_SLEW_DPS`. - Want softer and adjustable lower cruise speed and acceleration: ```python SERVO_SLEW_DPS = 150.0 # Cruise angular velocity upper limit (degrees/second) SERVO_SLEW_ACCEL_DPS2 = 3000.0 # Upper limit of angular acceleration (degrees/second ²) ``` - Insufficient current supply to the servo may also cause jitter and abnormal noise. Please check whether the servo exclusively occupies one output channel of the step-down voltage regulator module, whether the voltage of this channel is within 5.0~5.5V, and whether it shares a common ground with the main control board; if necessary, connect a 470~1000 µF capacitor in parallel near the power supply terminal of the servo. ## The distance reading occasionally displays --.- **Phenomenon:** During operation, it occurs every few seconds `--.-`. **Solution:** This is the inherent 5~7% point loss of the module (see "Actual Performance Test Data"), which is a normal phenomenon rather than a wiring issue. The presentation can be adjusted via parameters: ```python HOLD_FAILS = 3 # hold the last reading while consecutive missed points <= N (0 = disable) HISTORY_GAP_BRIDGE = 2 # max gap (points) bridged by a dashed line in the history chart (0 = off) ``` ## Ranging fails continuously when rotated to certain angles **Phenomenon:** When scanning to a certain angle interval, the readings fail continuously, and return to normal after leaving this angle. **Solution:** This is mostly caused by the instantaneous current of the servo pulling down the 5V power supply rail, which leads to abnormal operation of the ultrasonic module or the level conversion module. Please connect the servo and the main control board to **different output channels** of the step-down voltage regulator module, and ensure that the two are **commonly grounded**; at the same time, confirm that the power supply for the ultrasonic module and the level conversion module is not tapped from the servo's power line. If the two channels share the same output, please rewire them to separate channels. ## The judgment result is reversed left and right **Phenomenon:** When the interface displays `LEFT`, the physical probe actually faces to the right. **Solution:** Simply toggle the `mirror` switch in src/config.py (only modify the definition of "whether 0° counts as left or right" — pulse width and stroke calibration remain unaffected): ```python SERVO_INVERT = False # default True ``` You can use the calibration script to check each angle one by one: the script will make the probe pause for 3 seconds at 0°/90°/180° respectively, and print the orientation recognized by the program; you only need to check whether the actual object matches the prompt. ## Runtime Prompt: No module named 'PySide6' **Phenomenon:** ``` ERROR: PySide6 is not installed. ``` **Solution:** Dependencies failed to install successfully. Please go back to the project root directory and reinstall them: ```bash cd demo-ultrasonic-sensing python3 -m venv .venv .venv/bin/python -m pip install -r requirements.txt \ -i https://mirrors.aliyun.com/pypi/simple/ \ --trusted-host mirrors.aliyun.com ``` ## Running python src/main.py directly throws an import error **Phenomenon:** ``` ImportError: attempted relative import with no known parent package ``` **Solution:** The package import of this project is based on the project root directory. Please switch to module mode to start it: ```bash python -m src.main ``` or directly run the startup script `./run_scan.py`. # Technical Support and Contributions If you encounter any issues during use, please feel free to [submit a technical inquiry on the Quectel Official Forum](), and our technical support team will respond to you promptly. Project Open Source Repository: https://github.com/Quectel-Pi/demo-ultrasonic-sensing Feel free to submit an Issue to report problems or a Pull Request to contribute code improvements!